Effective Java 2.0_中文版_Item 10

文章作者:Tyan
博客:noahsnail.com | CSDN | 简书

Item10: 总是重写toString方法

尽管java.lang.Object提供了toString方法的实现,但是通常情况下它返回的字符串不是使用类的用户想要的。返回的字符串包含类名,后面是一个@符号加上哈希码的十六进制表示,例如PhoneNumber@163b91toString的通用约定指出,返回值应该是“简洁但易读的信息表示”[JavaSE6]。虽然可以认为PhoneNumber@163b91简洁易读,但它与(707) 867-5309相比,它的信息不够丰富。toString约定进一步指出,“建议所有的子类重写这个方法”。确实是个好建议。

虽然它不像遵守equalshashCode约定(Item 8, Item 9)那样重要,但是提供一个好的toString实现可以使你的类用起来更舒适。当对象传到printlnprintf,字符串连接操作符,或assert中,或通过调试器打印时,会自动调用toString方法。(Java 1.5版本中平台加入了printf方法,相关的方法包括String.format,类似于C语言中的sprintf方法)。

如果你已经为PhoneNumber提供了一个好的toString方法,生成有用的诊断信息是很容易的:

1
System.out.println("Failed to connect: " + phoneNumber);

无论你是否重写toString方法,程序员们都会以这种方式生成诊断信息,但除非你重写了toString方法,否则这些信息是无用的。提供一个好的toString方法的好处是除了类的实例之外,也扩展了包含这些实例引用的对象,尤其是集合。当打印一个映射时,{Jenny=PhoneNumber@163b91}{Jenny=(707) 867-5309}你更喜欢哪一个?

当实践时,toString方法应该返回包含在对象中的所有的感兴趣信息,正如刚才电话号码的例子展示的那样。如果对象很大或它包含不能用字符串表示的状态,重写toString方法是不切实际的。在这种情况下,toString应该返回一个概要信息,例如Manhattan white pages (1487536 listings)Thread[main,5,main]。理想情况下,字符串应该是自解释的。(Thread例子不能满足这样的要求。)

当实现toString时,你要做的一个重要决定是是否在文档中指定返回值的形式。对于值类建议你这样做,例如电话号码或矩阵。指定返回值形式的优势在于它能为对象提供一个标准的,清晰的,可读的表示。这个表示可以用在输入输出中,也可以用在一致的可读数据对象中,例如XML文档。如果你指定了这个形式,提供一个匹配的静态工厂或构造函数通常是一个好主意,程序员可以很容易地在对象和它的字符串表示之间来回转换。Java平台库中许多值类都采用了这个方法,包括BigIntegerBigDecimal和大多数基本类型的包装类。

指定toString返回值形式的劣势在于一旦你指定了它,假设你的类被广泛使用,你就必须一直坚持它。程序员将会写代码转换这种表示,产生这种形式并将它嵌入到持久化数据中。如果你在将来的版本中更改了表示形式,你将会破坏他们的代码和数据,他们将会抱怨。如果你没有指定一个形式,你保留了添加信息的灵活性或者在后续版本改进这种形式。

无论你决定是否指定格式,你都应该清楚地表明你的意图。如果你指定了格式,你应该准确的去做。例如,下面的Item 9中PhoneNumber类的toString方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 /**
* Returns the string representation of this phone number.
* The string consists of fourteen characters whose format
* is "(XXX) YYY-ZZZZ", where XXX is the area code, YYY is
* the prefix, and ZZZZ is the line number. (Each of the
* capital letters represents a single decimal digit.)
*
* If any of the three parts of this phone number is too small
* to fill up its field, the field is padded with leading zeros.
* For example, if the value of the line number is 123, the last * four characters of the string representation will be "0123". *
* Note that there is a single space separating the closing
s* parenthesis after the area code from the first digit of the * prefix.
*/
@Override public String toString() {
return String.format("(%03d) %03d-%04d",areaCode, prefix, lineNumber);
}

如果你没有指定格式,文档注释读起来应该如下:

1
2
3
4
5
/**
* Returns a brief description of this potion. The exact details * of the representation are unspecified and subject to change, * but the following may be regarded as typical:
*
* "[Potion #9: type=love, smell=turpentine, look=india ink]" */
@Override public String toString() { ... }

写代码或持久化数据的依赖于格式细节的程序员,在读了这个文档之后,一旦格式改变,只能自己负责后果。

无论你是否指定了格式,都应该提供toString返回值中包含的所有信息的程序访问接口。例如,PhoneNumber类应该包含区域码,前缀和行号的访问器。如果你没有这样做,你会迫使需要这个信息的程序员取转换这个字符串。除了为程序员降低效率和造成不必要的工作之外,这个过程中很容易出错,而且会导致系统非常脆弱,如果你更改了格式系统会崩溃。如果没有提供访问器,即使你指明了字符串格式是可以变化的,这个字符串格式也变成了实际上的API。

如果有收获,可以请我喝杯咖啡!